<?php

	/**
	 * @see Bike_Db_Select
	 */
	require_once 'Bike/Db/Select.php';
	
	/**
	 * @see Bike_Model_Mapper_Abstract
	 */
	require_once 'Bike/Model/Mapper/Abstract.php';


	class Bike_Db_Mapper_Select extends Bike_Db_Select
	{        const DEPENDENCES_INNER = Bike_Model_Mapper_Abstract::DEPENDENCES_INNER;
    	const DEPENDENCES_ALL = Bike_Model_Mapper_Abstract::DEPENDENCES_ALL;

        protected $_mapperClass;
        protected $_dependencesType;
        protected $_dependences = array();
        protected $_dbFieldNames = array();

        public function __construct(Bike_Model_Mapper_Abstract $Mapper)
	    {
	        parent::__construct($Mapper->getAdapter());
	        $this->setMapperClass(get_class($Mapper));
	    }

		public function getMapperClass()
	    {
	        return $this->_mapperClass;
	    }

	    protected function setMapperClass($MapperClass)
	    {
	        $this->_mapperClass = $MapperClass;
	        return $this;
	    }

	    public function getMapper()
	    {
	        return call_user_func(array($this->getMapperClass(), 'getInstance'));
	    }

	    public function setDependencesType($Type)
	    {
	    	$this->_dependencesType = $Type;
	    	return $this;
	    }

	    public function getDependencesType()
	    {
	    	if(!in_array($this->_dependencesType, array(self::DEPENDENCES_ALL, self::DEPENDENCES_INNER)))
	    	{
	    		$this->_dependencesType = self::DEPENDENCES_ALL;
	    	}
	    	return $this->_dependencesType;
	    }

	    public function isInnerDependencesType()
	    {	    	return ($this->getDependencesType() == self::DEPENDENCES_INNER);
	    }

	    public function getDependences($Name = Null)
	    {	    	if(is_null($Name))
	    	{
	    		return (array) $this->_dependences;
	   		}
	   		if(!array_key_exists($Name, (array) $this->_dependences))
	   		{	   			require_once 'Zend/Db/Select/Exception.php';
	   			throw new Zend_Db_Select_Exception('Dependence with name "'.$Name.'" not exists');
	   		}
	   		return $this->_dependences[$Name];
	    }

	    public function getDependence($Name)
	    {
	    	return $this->getDependences($Name);
	    }

	    protected function addDependence($Dependence)
	    {	    	$name = $Dependence->getName();
	    	if(array_key_exists($name, $this->_dependences))
	    	{	    		require_once 'Zend/Db/Select/Exception.php';
	    		throw new Zend_Db_Select_Exception('Dependence with name "'.$name.'" already exists');
	    	}
	    	$this->_dependences[$name] = $Dependence;
	    	return $this;
	    }

	    public function joinDependences(array $Dependences, array $Options=null, $NamePrefix='')
		{
			foreach($Dependences as $dependence)
			{
				$dependence->join($this, $Options, $NamePrefix);
				$this->addDependence($dependence);
			}
			return $this;
		}

		public function joinInnerDependences(array $Dependences, array $Options=null, $NamePrefix='')
		{
			$this->setDependencesType(self::DEPENDENCES_INNER);
			$this->joinDependences($Dependences, $Options, $NamePrefix);
			return $this;
		}

		public function joinDependencesByFields(array $Dependences, array $Fields, array $Options=null, $NamePrefix='')
		{
			foreach($Dependences as $dependence)
			{
				try
				{
					$dependence->joinByFields($this, $Fields, $Options, $NamePrefix);
					$this->addDependence($dependence);
				}
				catch(Bike_Model_Exception $e)
				{				}
			}
			return $this;
		}

		protected function _replaceAliasesToDbFieldNames($String, $WithQuotes=True)
		{			$symbol = $this->getAdapter()->getQuoteIdentifierSymbol();
			preg_match_all("/[".$symbol."]?([\w]{1}[_\w\d]+)[".$symbol."]?/s", $String, $matches);
	        $dirtyFieldNames = $matches[0];
	        $fieldNames = $matches[1];
	        $dbFieldNames = array();
	        foreach($fieldNames as $i => $fieldName)
	        {
	        	try
	        	{
	        		$dbFieldName = $this->convertAliasToDbFieldName($fieldName);
	        		$dbFieldNames[$i] = ($WithQuotes) ? $this->getAdapter()->quoteIdentifier($dbFieldName) : $dbFieldName;
				}
	        	catch(Zend_Db_Select_Exception $e)
	        	{
	        		unset($fieldNames[$i]);
	        		unset($dirtyFieldNames[$i]);
	        	}
	        }
            if(sizeof($fieldNames))
            {
            	$String = str_replace($dirtyFieldNames, $dbFieldNames, $String);
            }
            return $String;
		}

	    protected function getDbFieldName($Alias)
	    {	    	if(isset($this->_dbFieldNames[$Alias]))
	    	{
	    		return $this->_dbFieldNames[$Alias];
	   		}
	    }

	    protected function setDbFieldName($Alias, $Name)
	    {	 		$this->_dbFieldNames[$Alias] = $Name;
	    }

	    public function getFieldNameWithTabAlias($FieldName, $TabAlias=Null)
	    {	    	if(is_null($TabAlias))
	    	{	    		$TabAlias = $this->getMapper()->getTableAlias();
	    	}
	    	return $TabAlias.'.'.$FieldName;
	    }

	    public function convertAliasToDbFieldName($Alias)
	    {			$dbFieldName = $this->getDbFieldName($Alias);
			if($dbFieldName)
			{				return $dbFieldName;
			}
			if(in_array($Alias, $this->getMapper()->getDbTable()->info(Bike_Db_Table::COLS)))
			{
				$dbFieldName = $this->getFieldNameWithTabAlias($Alias);
       			$this->setDbFieldName($Alias, $dbFieldName);
       			return $dbFieldName;
			}
			$depKeys = array_keys($this->getDependences());
			$lengths = array();
			foreach($depKeys as $depKey)
			{				$pos = strpos($Alias, $depKey);
				if($pos === 0)
				{					$lengths[strlen($depKey)] = $depKey;
				}
			}
			krsort($lengths);
			foreach($lengths as $depKey)
			{				$dependence = $this->getDependence($depKey);
				try
        		{
        			$fieldName = $dependence->getFieldNameFromAlias($Alias);
        			$dbFieldName = $this->getFieldNameWithTabAlias($fieldName, $dependence->getDbTableAlias());
        			$this->setDbFieldName($Alias, $dbFieldName);
        			return $dbFieldName;
        		}
        		catch(Bike_Model_Exception $e)
        		{
        		}
			}
			require_once 'Zend/Db/Select/Exception.php';
			throw new Zend_Db_Select_Exception('Can not conver alias "'.$Alias.'"');
	    }

	    protected function _where($condition, $value = null, $type = null, $bool = true)
	    {
	        $condition = $this->_replaceAliasesToDbFieldNames($condition);
	        return parent::_where($condition, $value, $type, $bool);
	    }

	    /**
	     * Specifies the columns used in the FROM clause.
	     *
	     * The parameter can be a single string or Zend_Db_Expr object,
	     * or else an array of strings or Zend_Db_Expr objects.
	     *
	     * @param  array|string|Zend_Db_Expr $cols The columns to select from this table.
	     * @param  string $correlationName Correlation name of target table. OPTIONAL
	     * @return Zend_Db_Select This Zend_Db_Select object.
	     */
	    public function columns($cols = '*', $correlationName = null)
	    {
	        if(!is_array($cols))
	        {
	            $cols = array($cols);
	        }
	        foreach($cols as $n => $col)
	        {
	        	$cols[$n] = $this->_replaceAliasesToDbFieldNames($col, $WithQuotes=False);
	        }
	        parent::columns($cols, $correlationName);

	        return $this;
	    }

	    public function group($spec)
	    {	    	if(!is_array($spec))
	    	{
            	$spec = array($spec);
        	}
        	foreach($spec as $i => $val)
        	{
	            $spec[$i] = $this->_replaceAliasesToDbFieldNames($val, $WithQuotes=False);
	        }
	        return parent::group($spec);
	    }

	    public function order($spec)
	    {
	    	if(!is_array($spec))
	    	{
            	$spec = array($spec);
        	}
        	foreach($spec as $i => $val)
        	{
	            $spec[$i] = $this->_replaceAliasesToDbFieldNames($val, $WithQuotes=False);
	        }
	        return parent::order($spec);
	    }
	}
